iT邦幫忙

2025 iThome 鐵人賽

DAY 17
0

  目前為止,我們已經完成了 LLaMA2 模型的基本架構,但如果要真正開始訓練,第一步就必須先處理文字的輸入,在 NLP 任務中,Tokenizer 負責把「自然語言」轉換成「數字序列 (tokens)」,這些 token 才能進一步送進模型中,今天我們就來看看常見的 Tokenizer 類型,並動手訓練一個屬於我們自己的 BPE Tokenizer。

1.為什麼需要 Tokenizer?

我們的語言是字母、詞彙與標點符號的組合,電腦並不能直接理解,舉例像是:

輸入: "Hello, world!"
輸出: [1543, 11, 872, 0]   # 轉換成 token ids

不同的 Tokenizer 的切割方法,會影響到詞典大小 (vocab size)、模型的泛化能力、訓練效率與推理速度。

2.常見的 Tokenizer 類型

Word-based Tokenizer

這是最簡單的方式,直接以空格和標點來分割,優點就是比較直觀,但比較不適合處理未收錄詞、中文等無空格語言

Input: "Hello, world!"
Output: ["Hello", ",", "world", "!"]

Character-based Tokenizer

逐字切分,每個字就是一個 token,優點是不會有 OOV 適合所有語言,但缺點就是會讓序列太長,語義單位太小,導致訓練效率極低。

Input: "Hello"
Output: ["H", "e", "l", "l", "o"]

Subword Tokenizer

介於「字」和「詞」之間,是目前最常見的 NLP 分詞方式。

  • BPE (Byte Pair Encoding)
    基於統計合併高頻字元對,例如:
Input: "lower"   → ["low", "er"]
Input: "newest"  → ["new", "est"]
  • WordPiece (BERT 使用)
    最大化子詞序列的機率:
Input: "unhappiness" → ["un", "##happiness"]
  • Unigram (SentencePiece 使用)
    基於機率模型,選擇最佳子詞切分:
Input: "newest" → ["new", "est"]

3.動手訓練一個 BPE Tokenizer

我們選擇 Hugging Face tokenizers 來訓練。

  • Step 1.安裝依賴
pip install tokenizers datasets transformers
  • Step 2.讀取資料
import json
import os

def read_texts_from_jsonl(file_path: str):
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            data = json.loads(line)
            if 'text' in data:
                yield data['text']
  • Step 3.設定 Tokenizer
def create_tokenizer_config(save_dir: str):
    config = {
        "bos_token": "<|im_start|>",
        "eos_token": "<|im_end|>",
        "unk_token": "<unk>",
        "pad_token": "<pad>",
        "tokenizer_class": "PreTrainedTokenizerFast",
    }
    with open(os.path.join(save_dir, "tokenizer_config.json"), "w", encoding="utf-8") as f:
        json.dump(config, f, ensure_ascii=False, indent=4)
  • Step 4.訓練 BPE Tokenizer
from tokenizers import Tokenizer, models, pre_tokenizers, decoders, trainers
from tokenizers.normalizers import NFKC

def train_tokenizer(data_path: str, save_dir: str, vocab_size: int = 8192):
    os.makedirs(save_dir, exist_ok=True)

    tokenizer = Tokenizer(models.BPE(unk_token="<unk>"))
    tokenizer.normalizer = NFKC()
    tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False)
    tokenizer.decoder = decoders.ByteLevel()

    special_tokens = ["<unk>", "<s>", "</s>", "<|im_start|>", "<|im_end|>", "<pad>"]
    trainer = trainers.BpeTrainer(
        vocab_size=vocab_size,
        special_tokens=special_tokens,
        min_frequency=2,
        show_progress=True,
        initial_alphabet=pre_tokenizers.ByteLevel.alphabet()
    )

    texts = list(read_texts_from_jsonl(data_path))
    tokenizer.train_from_iterator(texts, trainer=trainer, length=len(texts))

    tokenizer.save(os.path.join(save_dir, "tokenizer.json"))
    create_tokenizer_config(save_dir)
    print(f"Tokenizer saved to {save_dir}")
  • Step 5.測試 Tokenizer
from transformers import AutoTokenizer

def eval_tokenizer(tokenizer_path: str):
    tokenizer = AutoTokenizer.from_pretrained(tokenizer_path)

    print("Vocab size:", len(tokenizer))
    print("Special tokens:", tokenizer.all_special_tokens)

    test_text = "<|im_start|>user\nHello<|im_end|>"
    encoded = tokenizer(test_text).input_ids
    decoded = tokenizer.decode(encoded, skip_special_tokens=False)
    print("Original:", test_text)
    print("Decoded:", decoded)

參考連結:
https://datawhalechina.github.io/happy-llm/#/


上一篇
[Day16] 實作一個 LLaMA2 模型 (三)
下一篇
[Day18] 預訓練一個小型 LLM
系列文
從上下文工程到 Agent:30 天生成式 AI 與 LLM 學習紀錄21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言